package singsong;

import java.util.List;

public class FrequencyAnalyzer {
	public static final float DURATION_DIFF_FRACTION = 1.5f;
	public static final float WAVE_DIFF_THRESHOLD = 0.10f;
	public static final int WAVE_DIFF_NUM_SAMPLES = 100;
	public static final int MAX_PEAKS_BUFFERED = 40;
	
	private PeakToPeakGenerator peakGenerator;

	private AudioClipComparator clipComparator =
		new AudioClipComparator(WAVE_DIFF_THRESHOLD, WAVE_DIFF_NUM_SAMPLES);

	private PiecewiseAudioClip buffer = new PiecewiseAudioClip();
	
	private AudioClip signature = null;
	
	private float frequency = 0;
	
	private int samplesPerSecond = 0;
	
	private int sampleCount = 0;
	
	public FrequencyAnalyzer(int samplesPerSecond) {
		this.samplesPerSecond = samplesPerSecond;
		peakGenerator = new PeakToPeakGenerator(samplesPerSecond);
	}
	
	public void addSample(float sample) {
		peakGenerator.addSample(sample);
		++sampleCount;
		if (!peakGenerator.getAudioClipQueue().isEmpty()) {
			AudioClip clip = peakGenerator.getAudioClipQueue().remove();
			if (clip.getVolume() < AudioConstants.MIN_AMP) {
				signature = null;
				if (!buffer.getClips().isEmpty()) {
					buffer = new PiecewiseAudioClip();
				}
			} else {
				buffer.Append(clip);
				if (signature == null) {
					if (detectSignature()) {
						float end = ((float) sampleCount) / samplesPerSecond;
						float start = end - signature.getDuration();
						//System.out.printf("start=%f end=%f duration=%f\n", start, end, signatureAudioClip.getDuration());
						buffer = new PiecewiseAudioClip();
					} else if (buffer.getClips().size() >= MAX_PEAKS_BUFFERED) {
						buffer.getClips().remove(0);
					}
				} else {
					if (buffer.getDuration() > (signature.getDuration() * DURATION_DIFF_FRACTION)) {
						signature = null;
						buffer = new PiecewiseAudioClip();
						float end = ((float) sampleCount) / samplesPerSecond; 
						//System.out.printf("terminate missing signature %f\n", end);
					} else if (detectRepeatedSignature()) {
						signature = buffer;
						buffer = new PiecewiseAudioClip();
						float end = ((float) sampleCount) / samplesPerSecond; 
						float start = end - signature.getDuration();
						//System.out.printf("start=%f end=%f duration=%f\n", start, end, signatureAudioClip.getDuration());
					}
				}
			}
			updateFrequency();
		}
	}

	private boolean detectSignature() {
		final List<AudioClip> CLIPS = buffer.getClips();
		final int N = CLIPS.size();
		for (int i = 2; i <= N; ++i) {
			for (int j = 1; j < i; ++j) {
				PiecewiseAudioClip left = new PiecewiseAudioClip(CLIPS.subList(N - i, N - j));
				PiecewiseAudioClip right = new PiecewiseAudioClip(CLIPS.subList(N - j, N));
				if ((right.getDuration() * DURATION_DIFF_FRACTION) < left.getDuration()) {
					continue;
				} else if (right.getDuration() > (left.getDuration() * DURATION_DIFF_FRACTION)) {
					break;
				} else {
					//System.out.printf("comp=%f\n", clipComparator.computeWaveDiffFrac(left, right));
					if (clipComparator.compare(left, right)) {
						signature = right;
						return true;
					}
				}
			}
		}
		return false;
	}
	
	private boolean detectRepeatedSignature() {
		return clipComparator.compare(signature, buffer);
	}

	private void updateFrequency() {
		if (signature != null) {
			frequency = 1.0f / signature.getDuration();
		}
	}

	public float getFrequency() {
		return frequency;
	}
}
